﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Xceed.Silverlight.Utils;
using Xceed.Utils.Licensing;
using Xceed.Silverlight.Compatibility;

namespace Xceed.Silverlight.DataGrid
{
  internal class DataGridPanel : Panel, IColumnPanelOwner, IXceedRoutedEventElement, IVisitorElement
  {
    #region Static Fields

    internal static readonly XceedRoutedEvent ColumnAdded = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent ColumnRemoved = new XceedRoutedEvent();

    #endregion

    #region Constructor

    public DataGridPanel()
    {
      m_eventHelper = new XceedEventHelper( this );
      m_columnPanel = new ColumnPanel( this );
      this.Children.Add( new RootHost() );
    }

    #endregion

    #region RenderedColumns Internal Property

    internal IEnumerable<ColumnContainer> RenderedColumns
    {
      get
      {
        return this.ColumnPanel.RenderedColumns;
      }
    }

    #endregion

    #region AllColumns Internal Property

    internal IEnumerable<ColumnContainer> AllColumns
    {
      get
      {
        return this.ColumnPanel.AllColumns;
      }
    }

    #endregion

    #region ColumnPanel Internal Property

    internal ColumnPanel ColumnPanel
    {
      get
      {
        return m_columnPanel;
      }
    }

    #endregion

    #region EventManager Internal Property

    internal XceedEventHelper EventManager
    {
      [DebuggerStepThrough]
      get
      {
        return m_eventHelper;
      }
    }

    private readonly XceedEventHelper m_eventHelper;

    #endregion

	  #region RootHost Internal Property

    internal RootHost RootHost
    {
      get
      {
        return ( RootHost )this.Children[ this.RootContainerIndex ];
      }
    }

	  #endregion

    #region FixedHeaders Internal Property

    internal IEnumerable<UIElement> FixedHeaders
    {
      get
      {
        return CompatibilityUtils.GetEnumerable(this.Children)
          .Take( this.RootContainerIndex );
      }
    }

    #endregion

    #region FixedFooters Internal Property

    internal IEnumerable<UIElement> FixedFooters
    {
      get
      {
        return CompatibilityUtils.GetEnumerable(this.Children)
          .Skip( this.RootContainerIndex + 1 );
      }
    }

    #endregion

    #region RootContainerIndex Internal Property

    internal int RootContainerIndex
    {
      get
      {
        for(int i=0;i<this.Children.Count;i++)
        {
          if( this.Children[i] is RootHost )
            return i;
        }

        throw new DataGridInternalException();
      }
    }

    #endregion

	  #region PendingTransitionContext Internal Property

    internal TransitionContext PendingTransitionContext
    {
      get;
      set;
    }

	  #endregion

	  #region ColumnElementHosts Internal Property

    internal IEnumerable<IColumnElementHost> ColumnElementHosts
    {
      get
      {
        ColumnElementHostExtractorVisitor visitor = new ColumnElementHostExtractorVisitor();
        this.Accept( visitor );
        // Do not return the visitor.Host collection. We want the returned IEnumerable
        // to be dynamic and recalculated on each enumration.
        foreach( IColumnElementHost host in visitor.Hosts )
        {
          yield return host;
        }
      }
    }

    #endregion

    #region ColumnPanelMeasureManager Private Property

    private IManageColumnsMeasure ColumnPanelMeasureManager
    {
      get
      {
        return ( IManageColumnsMeasure )this.GetElementOrParentBase<IManageColumnsMeasure>();
      }
    }

    #endregion

    #region UnconstrainedStateChanged Internal Event

    internal event EventHandler UnconstrainedStateChanged;

    private void OnUnconstrainedStateChanged()
    {
      if( this.UnconstrainedStateChanged != null )
      {
        this.UnconstrainedStateChanged( this, EventArgs.Empty );
      }
    }

    #endregion

    protected override Size MeasureOverride( Size availableSize )
    {
      bool isInDesign = DesignerProperties.GetIsInDesignMode();

      if( double.IsInfinity( availableSize.Width ) || double.IsNaN( availableSize.Width ) )
      {
        //If in an unconstrained environement and debugging
        if( !isInDesign && Debugger.IsAttached )
        {
          //Warn the developer the size of the grid has been arbitrary fixed.
          this.OnUnconstrainedStateChanged();
        }

        //Temporary value for design time purpose.
        availableSize.Width = 640d;
      }

      if( double.IsInfinity( availableSize.Height ) || double.IsNaN( availableSize.Height ) )
      {
        //If in an unconstrained environement and debugging
        if( !isInDesign && Debugger.IsAttached )
        {
          //Warn the developer the size of the grid has been arbitrary fixed.
          this.OnUnconstrainedStateChanged();
        }

        //Temporary value for design time purpose.
        availableSize.Height = 480d;
      }

      double columnPanelWidth = this.ColumnPanelMeasureManager.OnMeasureOverride( availableSize ).Width;

      //Invalidate the measure of all the column element hosts in order
      //to have the cells not clipped when the ColumnWidth changes.
      this.InvalidateColumnHostsMeasure();

      Size measuredSize = new Size( Math.Min( columnPanelWidth, availableSize.Width) , 0 );
      int rootContainerIndex = this.RootContainerIndex;

      Action<int> measureChild = (i) =>
      {
        UIElement child = this.Children[ i ];
        child.Measure( availableSize );
        availableSize.Height = Math.Max( 0d, availableSize.Height - child.DesiredSize.Height );
        measuredSize.Height += child.DesiredSize.Height;
      };

      //Measure FixedHeaders
      for( int i = 0; i < rootContainerIndex; i++ )
      {
        measureChild( i );
      }

      //Measure FixedFooters
      for( int i = rootContainerIndex+1; i < this.Children.Count; i++ )
      {
        measureChild( i );
      }

      RootHost rootContainer = ( RootHost )this.Children[ rootContainerIndex ];

      //Measure the RootContainer
      rootContainer.Measure( availableSize );

      Size containerSize = rootContainer.DesiredSize;
      measuredSize.Height += Math.Min( availableSize.Height, containerSize.Height );

      return measuredSize;
    }

    protected override Size ArrangeOverride( Size finalSize )
    {
      int rootContainerIndex = this.RootContainerIndex;

      m_columnPanel.Arrange( finalSize, new LayoutParameters( this.PendingTransitionContext ) );
      this.PendingTransitionContext = null;

      //Invalidte the arrange of all the column elements hosts in order
      //to have the cells layouted
      this.InvalidateColumnHostsArrange();

      double headersArrangeHeight = 0;
      double footersArrangeHeight = 0;
      Rect finalRect = new Rect( new Point( 0, 0 ), finalSize );
      for( int i = 0; i < rootContainerIndex; i++ )
      {
        UIElement child = this.Children[ i ];
        finalRect.Height = child.DesiredSize.Height;
        child.Arrange( finalRect );
        finalRect.Y += finalRect.Height;
        headersArrangeHeight += finalRect.Height;
      }

      finalRect = new Rect( new Point( 0, finalSize.Height ), finalSize );
      for( int i = this.Children.Count - 1; i > rootContainerIndex; i-- )
      {
        UIElement child = this.Children[ i ];
        finalRect.Height = child.DesiredSize.Height;
        finalRect.Y -= finalRect.Height;
        child.Arrange( finalRect );
        footersArrangeHeight += finalRect.Height;
      }


      finalRect = new Rect( 0,
        headersArrangeHeight,
        finalSize.Width,
        Math.Max( 0d, finalSize.Height - headersArrangeHeight - footersArrangeHeight ) );

      this.Children[rootContainerIndex].Arrange( finalRect );

      return finalSize;
    }

    internal void Accept( IVisitor visitor )
    {
      visitor.Visit( this );
    }

    internal void EndTransition()
    {
      this.ColumnPanel.EndTransition();

      this.InvalidateColumnHostsArrange();
    }

    internal void SetHeadersFooters( IList<UIElement> headers, IList<UIElement> footers )
    {
      //Remove all but the RootContainer
      int rootContainerIndex = this.RootContainerIndex;
      int headersCount = this.RootContainerIndex;
      int footersCount = this.Children.Count - this.RootContainerIndex - 1;

      bool updateHeaders = !this.FixedHeaders.SequenceEqual(headers);
      bool updateFooters = !this.FixedFooters.SequenceEqual(footers);

      if(updateHeaders)
      {
        //Remove current headers
        for(int i=0;i<headersCount;i++)
        {
          this.Children.RemoveAt(0);
        }
        headersCount = 0;
      }

      if(updateFooters)
      {
        //Remove current footers
        for( int i = 0; i < footersCount; i++ )
        {
          this.Children.RemoveAt(headersCount + 1);
        }
      }

      if(updateHeaders)
      {
        for(int i=0;i<headers.Count;i++)
        {
          this.Children.Insert(i,headers[i]);
        }
      }

      if(updateFooters)
      {
        for(int i=0;i<footers.Count;i++)
        {
          this.Children.Add(footers[i]);
        }
      }
    }

    private void InvalidateColumnHostsMeasure()
    {
      foreach( IColumnElementHost host in this.ColumnElementHosts )
      {
        host.InvalidateColumnMeasure();
      }
    }

    private void InvalidateColumnHostsArrange()
    {
      foreach( IColumnElementHost host in this.ColumnElementHosts )
      {
        host.InvalidateColumnArrange();
      }
    }

    #region IVisitorElement Members

    void IVisitorElement.Accept( IVisitor visitor )
    {
      this.Accept( visitor );
    }

    #endregion

    #region IColumnPanelOwner Members

    void IColumnPanelOwner.OnColumnAdded( ColumnContainer added )
    {
      this.EventManager.RaiseEvent( DataGridPanel.ColumnAdded, new ColumnContainerEventArgs( this, added ) );
    }

    void IColumnPanelOwner.OnColumnRemoved( ColumnContainer removed )
    {
      this.EventManager.RaiseEvent( DataGridPanel.ColumnRemoved, new ColumnContainerEventArgs( this, removed ) );
    }

    void IColumnPanelOwner.InvalidatePanelMeasure()
    {
        this.InvalidateMeasure();
    }

    void IColumnPanelOwner.InvalidatePanelArrange()
    {
        this.InvalidateArrange();
    }

    #endregion

    #region IXceedRoutedEventElement Members

    XceedEventHelper IXceedRoutedEventElement.EventManager
    {
      [DebuggerStepThrough]
      get
      {
        return this.EventManager;
      }
    }

    #endregion

    #region Private Fields

    private readonly ColumnPanel m_columnPanel;

    #endregion
  }
}
